package com.haifeng.wdsh.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.haifeng.wdsh.common.base.constants.Constants;
import com.haifeng.wdsh.common.base.exception.BusinessException;
import com.haifeng.wdsh.common.base.exception.ErrorMsg;
import com.haifeng.wdsh.common.base.util.JsonUtil;
import com.haifeng.wdsh.common.base.vo.PermissionVo;
import com.haifeng.wdsh.common.base.vo.UserVo;
import com.haifeng.wdsh.common.redis.utils.RedisUtil;
import com.haifeng.wdsh.gateway.properties.AuthProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * <p>
 *  Token校验器
 * </p>
 *
 * @author: Haifeng
 * @date: 2020-05-06
 */
@Order(3)
@Component
public class TokenFilter extends AbstractGatewayFilterFactory {

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    private final AuthProperties authProperties;
    private final RedisUtil redisUtil;

    public TokenFilter(AuthProperties authProperties, RedisUtil redisUtil) {
        this.authProperties = authProperties;
        this.redisUtil = redisUtil;
    }

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            // 如果不需要鉴权，则跳过
            Iterator<String> iterator = authProperties.getPassAuth().iterator();
            while (iterator.hasNext()){
                if (antPathMatcher.match(iterator.next(),path)){
                    return chain.filter(exchange);
                }
            }
            // 检查头信息是否包含Authorization的Token信息
            if (!request.getHeaders().containsKey(Constants.AUTHORIZATION_HEADER_NAME)){
                throw new BusinessException(HttpStatus.BAD_REQUEST.value(), ErrorMsg.REQUIRED_STRING_PARAMETER_TOKEN_IS_NOT_PRESENT);
            }
            String authorization = request.getHeaders().getFirst(Constants.AUTHORIZATION_HEADER_NAME);
            if (!authorization.startsWith(Constants.BEARER) || authorization.length() <= Constants.BEARER.length()){
                throw new BusinessException(HttpStatus.BAD_REQUEST.value(), ErrorMsg.NOT_START_WITH_BEARER);
            }
            String token = authorization.substring(Constants.BEARER.length());
            // 解析Token
            Claims claims = null;
            try {
                claims = Jwts.parser().setSigningKey(Constants.JWT_SIGN_KEY.getBytes("UTF-8"))
                        .parseClaimsJws(token).getBody();
            } catch (Exception e) {
                if (e instanceof ExpiredJwtException){
                    throw new BusinessException(HttpStatus.BAD_REQUEST.value(), ErrorMsg.TOKEN_EXPIRED);
                }else {
                    throw new BusinessException(HttpStatus.BAD_REQUEST.value(), ErrorMsg.CANNOT_CONVERT_ACCESS_TOKEN_TO_JSON);
                }
            }
            String userName = (String)claims.get("user_name");
            // 此包含了我们自定义UserService封装的内容，比如我是用UserVo封装的
            UserVo userVo = JSON.parseObject(userName, UserVo.class);
            Set<PermissionVo> permissionVos = userVo.getPermissions();
            String username = userVo.getUsername();
            if (authProperties.getUseRedis() && redisUtil.hasKey(Constants.USER_+username)){
                // 从Redis中取出用户信息,达到修改权限实时生效的目的
                permissionVos = JSON.parseObject(redisUtil.get(Constants.USER_ + username), UserVo.class).getPermissions();
            }
            Boolean hasPermission = false;
            String realPath = getPath(path);
            if (!CollectionUtils.isEmpty(permissionVos)){
                for (PermissionVo permissionVo : permissionVos) {
                    if (antPathMatcher.match(permissionVo.getPermissionUrl(), realPath)) {
                        hasPermission = true;
                        break;
                    }
                }
            }
            if (!hasPermission){
                throw new BusinessException(HttpStatus.FORBIDDEN.value(), ErrorMsg.NO_PERMISSION);
            }
            // 将User信息放入请求头，供微服务使用,替换掉\n，否则报错\n后只能跟\t
            ServerHttpRequest newRequest = request.mutate().header(Constants.USER_INFO, JsonUtil.toJsonString(userVo).replaceAll("\n","")).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            return chain.filter(exchange);
        };
    }


    public String getPath(String path){
        List<String> servicePrefix = authProperties.getServicePrefix();
        for (int i = 0; i < servicePrefix.size(); i++) {
            if (path.startsWith(servicePrefix.get(i))){
                path = path.replace(servicePrefix.get(i),"");
                break;
            }
        }
        return path;
    }
}
